package trip

import (
	rentalpb "car_rental/rental/api/gen/v1"
	"car_rental/rental/trip/dao"
	"car_rental/shared/auth"
	"car_rental/shared/id"
	"context"
	"go.uber.org/zap"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"math/rand"
	"time"
)

type Service struct {
	ProfileManager
	CarManager
	PointManger
	*dao.Mongo
	*zap.Logger
}

// ProfileManager 个人
type ProfileManager interface {
	Verify(context.Context, id.AccountId) (id.IdentityId, error)
}

// CarManager 车辆
type CarManager interface {
	Verify(context.Context, id.CarId, *rentalpb.Location) error
	UnLock(context.Context, id.CarId) error
	Lock(c context.Context, cid id.CarId) error
}

// PointManger 地标
type PointManger interface {
	Resolve(context.Context, *rentalpb.Location) (string, error)
}

// CreateTrip 创建行程
func (s *Service) CreateTrip(ctx context.Context, req *rentalpb.CreateTripRequest) (*rentalpb.TripEntity, error) {

	s.Logger.Sugar().Infof("CreateTrip=>请求参数: %v", req)

	aid, err := auth.AccountIdFromContext(ctx)

	if err != nil {
		return nil, err
	}

	if req.CarId == "" || req.Start == nil {
		return nil, status.Error(codes.InvalidArgument, "")
	}

	// 验证驾驶者身份
	iId, err := s.ProfileManager.Verify(ctx, aid)

	if err != nil {
		return nil, status.Error(codes.FailedPrecondition, err.Error())
	}

	// 检查车辆状态

	carId := id.CarId(req.CarId)

	err = s.CarManager.Verify(ctx, carId, req.Start)

	if err != nil {
		return nil, status.Error(codes.FailedPrecondition, err.Error())
	}

	// 创建行程: 写入数据库，开始计费

	ls := s.calcCurrentStatus(ctx, &rentalpb.LocationStatus{
		Location:     req.Start,
		TimestampSec: nowFunc(),
	}, req.Start)

	tr, err := s.Mongo.CreateTrip(ctx, &rentalpb.Trip{
		AccountId:  aid.String(),
		CarId:      carId.String(),
		IdentityId: iId.String(),
		Status:     rentalpb.TripStatus_IN_PROGRESS,
		Start:      ls,
		Current:    ls,
	})

	if err != nil {
		s.Logger.Warn("无法创建行程", zap.Error(err))
		return nil, status.Error(codes.AlreadyExists, "")
	}

	// 车辆开锁（后台开锁）
	go func() {

		err = s.CarManager.UnLock(context.Background(), carId)

		if err != nil {
			s.Logger.Error("开锁失败", zap.Error(err))
		}

	}()

	return &rentalpb.TripEntity{
		Trip: tr.Trip,
		Id:   tr.Id.Hex(),
	}, nil

}

// GetTrip 根据ID获取行程
func (s *Service) GetTrip(ctx context.Context, req *rentalpb.GetTripRequest) (*rentalpb.Trip, error) {

	s.Logger.Sugar().Infof("GetTrip=>请求参数: %v", req)

	aid, err := auth.AccountIdFromContext(ctx)

	if err != nil {
		return nil, err
	}

	tr, err := s.Mongo.GetTrip(ctx, id.TripId(req.Id), aid)

	if err != nil {
		return nil, status.Error(codes.Unimplemented, "")
	}

	return tr.Trip, nil

}

// GetTrips 根据状态获取行程
func (s *Service) GetTrips(ctx context.Context, req *rentalpb.GetTripsRequest) (*rentalpb.GetTripsResponse, error) {

	s.Logger.Sugar().Infof("GetTrips=>请求参数: %v", req)

	s.Logger.Sugar().Infof("GetTrip=>请求参数: %v", req)

	aid, err := auth.AccountIdFromContext(ctx)

	if err != nil {
		return nil, err
	}

	trs, err := s.Mongo.GetTrips(ctx, aid, req.Status)

	if err != nil {
		s.Logger.Error("无法获取到Trips", zap.Error(err))
		return nil, status.Error(codes.Internal, "")
	}

	res := &rentalpb.GetTripsResponse{}

	for _, tr := range trs {

		res.Trips = append(res.Trips, &rentalpb.TripEntity{
			Trip: tr.Trip,
			Id:   tr.Id.Hex(),
		})

	}

	return res, nil

}

// UpdateTrip 更新行程
func (s *Service) UpdateTrip(ctx context.Context, req *rentalpb.UpdateTripRequest) (*rentalpb.Trip, error) {

	s.Logger.Sugar().Infof("UpdateTrip=>请求参数: %v", req)

	aid, err := auth.AccountIdFromContext(ctx)

	if err != nil {
		return nil, err
	}

	tid := id.TripId(req.Id)

	tr, err := s.Mongo.GetTrip(ctx, id.TripId(req.Id), aid)

	if err != nil {
		return nil, status.Error(codes.NotFound, "")
	}

	if tr.Trip.Current == nil {
		s.Logger.Error("行程数据有误", zap.Stringer("id", tid))
		return nil, status.Error(codes.Internal, "")
	}

	cur := tr.Trip.Current.Location

	if req.Current != nil {
		cur = req.Current
	}

	if req.Current != nil {
		tr.Trip.Current = s.calcCurrentStatus(ctx, tr.Trip.Current, cur)
	}

	if req.EndTrip {
		tr.Trip.End = tr.Trip.Current
		tr.Trip.Status = rentalpb.TripStatus_FINISHED
	}

	// 乐观锁
	err = s.Mongo.UpdateTrip(ctx, tid, aid, tr.UpdatedAt, tr.Trip)

	return nil, err

}

const centPerSec = 0.7

var nowFunc = func() int64 {
	return time.Now().Unix()
}

// 计算当前状态
func (s *Service) calcCurrentStatus(ctx context.Context, last *rentalpb.LocationStatus, cur *rentalpb.Location) *rentalpb.LocationStatus {

	now := nowFunc()

	elapsedSec := float64(now - last.TimestampSec)

	pointName, err := s.PointManger.Resolve(ctx, cur)

	if err != nil {
		s.Logger.Info("无法处理Point", zap.Stringer("location", cur))
	}

	return &rentalpb.LocationStatus{
		Location:     cur,
		FeeCent:      last.FeeCent + int32(centPerSec*elapsedSec*2*rand.Float64()),
		KmDriven:     last.KmDriven + 0.02*elapsedSec*2*rand.Float64(),
		PointName:    pointName,
		TimestampSec: now,
	}

}
